/**
 * A JavaScript String util
 *
 * it's a static class, defined as a object.
 *
 * @package js.lang
 * @author: DSONet (dsonet@msn.com)
 * @version $Id: JString.js 35 2008-03-02 17:01:34Z dsonet $
 */
/**
 * A JavaScript String util
 *
 */
var JString = {
	/** 
	 * 返回不带前导与后续空格的字符串副本 
	 * @public
	 * @param {string} s 字符串
	 * @return {string} 不带或前导与后续空格的字符串副本
	 * @id JString.trim
	 */
	trim : function(s)
	{
		return s.replace(/(?:^\s*)|(?:\s*$)/g, "");
	}
	/** 
	 * 返回字符串长度(1个中文字符按2个计算) 
	 * @public
	 * @param {string} s 字符串
	 * @return {number} 长度值
	 * @id JString.Length
	 */
	, Length : function(s)
	{
		var arr = s.match(/[^\x00-\xff]/ig);
		return s.length + (arr == null ? 0 : arr.length);
	}
	/** 
	 * 返回不带前导空格的字符串副本 
	 * @public
	 * @param {string} s 字符串
	 * @return {string} 不带前导空格的字符串副本
	 * @id JString.trimLeft
	 */
	, trimLeft : function(s)
	{
		return s.replace(/(?:^\s*)/g, "");
	}
	/** 
	 * 返回不带后续空格的字符串副本 
	 * @public
	 * @param {string} s 字符串
	 * @return {string} 不带后续空格的字符串副本
	 * @id JString.trimRight
	 */
	, trimRight : function(s)
	{
		return s.replace(/(?:\s*$)/g, "");
	}

	/** 
	 * 比较两个字符串是否相同(忽视大小写) 
	 * @public
	 * @param {string} source 字符串
	 * @param {string} target 字符串
	 * @return {boolean} true为相同,false为不同
	 * @id JString.equalsIgnoreCase
	 */
	, equalsIgnoreCase : function(source,target){
		if(target!=null && target.length == source.length){
			return source.toLowerCase() == target.toLowerCase();
		}else{
			return false;
		}
	}
	/** 
	 * 返回指定数目的从字符串的左边算起的字符
	 * @public
	 * @param {string} s 字符串
	 * @param {number} num 字符串
	 * @return {string} 最左边指定数目的字符
	 * @id JString.left
	 */
	, left : function(s, num, mode)
	{
		if (! /\d+/.test(num))
			return s;

		var str = s.substr(0, num);

		if (! mode)
			return str;
		var n = JString.Length(str) - str.length;
		num = num - parseInt(n / 2);
		return s.substr(0, num);
	}
	/** 
	 * 从字符串右边返回指定数目的字符
	 * @public
	 * @param {string} s 字符串
	 * @param {number} num 字符串
	 * @return {string} 最右边指定数目的字符
	 * @id JString.right
	 */
	, right : function(s, num, mode)
	{
		if (! /\d+/.test(num))
			return s;

		var str = s.substr(s.length - num);

		if (! mode)
			return str;
		var n = JString.Length(str) - str.length;
		num = num - parseInt(n / 2);
		return s.substr(s.length - num);
	}
	/** 
	 * 计算字符串中指定字符的数目
	 * @public
	 * @param {string} str1 字符串
	 * @param {string} str2 要计算次数的字符
	 * @return {number} 指定字符出现的次数
	 * @id JString.count
	 */
	, count : function(str1, str2, mode)
	{
		return eval("str1.match(/(" + str2 + ")/g" + (mode ? "i" : "") + ").length");
	}
	/**
     * Formats a string replacing formatting specifiers with values provided as arguments
     * which are formatted according to the specifier.
     *  This is an implementation of  python's % operator for strings and is similar to sprintf from C.
     *  <pre>Usage:
     *       resultString = formatString.format(value1, v2, ...);
     *   Each formatString can contain any number of formatting specifiers which are
     *  replaced with the formated values.
     *   specifier([...]-items are optional):
     *       '%[(key)][flag][sign][min][percision]typeOfValue"
            (key)  If specified the 1st argument is treated as an object/associative array and the formating values
                     are retrieved from that object using the key.

            flag:
                0      Use 0s for padding.
                -      Left justify result, padding it with spaces.
                        Use spaces for padding.
            sign:
                +      Numeric values will contain a +|- infront of the number.
            min:
                l      The string will be padded with the padding character until it has a minimum length of l.
            percision:
               .x     Where x is the percision for floating point numbers and the lenght for 0 padding for integers.
            typeOfValue:
                d   Signed integer decimal.
                i   Signed integer decimal.
                b   Unsigned binary.                       //This does not exist in python!
                o   Unsigned octal.
                u   Unsigned decimal.
                x   Unsigned hexidecimal (lowercase).
                X   Unsigned hexidecimal (uppercase).
                e   Floating point exponential format (lowercase).
                E   Floating point exponential format (uppercase).
                f   Floating point decimal format.
                F   Floating point decimal format.
                c   Single character (accepts byte or single character string).
                s   String (converts any object using object.toString()).
        Examples:
            "%02d".format(8) == "08"
            "%05.2f".format(1.234) == "01.23"
            "123 in binary is: %08b".format(123) == "123 in binary is: 01111011"</pre>
    * @param {string} str  the string will be formated
    * @param {string} arguments  Each parameter is treated as a formating value.
    * @return {string} The formated String.
    * @public
 	* @id JString.format
    */
	, format : function(str)
	{
		var sf = str.match(/(%(\(\w+\)){0,1}[ 0-]{0,1}(\+){0,1}(\d+){0,1}(\.\d+){0,1}[dibouxXeEfFgGcrs%])|([^%]+)/g);

		if (sf)
		{
			if (sf.join("") != str)
			{
				throw new Error("Unsupported formating string.");
			}
		}
		else
		{
			throw new Error("Unsupported formating string.");
		};
		var rslt = sign = ""
			, s, frmt, o
			, cnt = 1
			, argsLen = arguments.length;

		for (var i = 0; i < sf.length; i++)
		{
			s = sf[i];

			if (s == "%%")
			{
				s = "%";
			}
			else if (s.slice(0, 1) == "%")
			{
				frmt = new FormatSpecifier(s);

				if (frmt.key)
				{
					if (Type.isObject(arguments[1]) && argsLen == 2)
					{
						o = arguments[1][frmt.key];
					}
					else
					{
						throw new Error("Object or associative array expected as formating value.");
					};
				}
				else
				{
					if (cnt >= argsLen)
					{
						throw new Error("Not enough arguments for format string");
					}
					else
					{
						o = arguments[cnt];
						cnt++;
					};
				};

				if (frmt.type == "s")
				{
					if (Type.isNull(o))
					{
						o = "null";
					};
					s = PadStr(o.toString(), frmt.paddingFlag, frmt.minLength);
				}
				else if (frmt.type == "c")
				{
					if (frmt.paddingFlag == "0")
					{
						frmt.paddingFlag = " ";
					};

					if (Type.isNumber(o))
					{
						s = PadStr(String.fromCharCode(o), frmt.paddingFlag, frmt.minLength);
					}
					else if (Type.isString(o))
					{
						if (o.length == 1)
						{
							s = PadStr(o, frmt.paddingFlag, frmt.minLength);
						}
						else
						{
							throw new Error("Character of length 1 required.");
						};
					}
					else
					{
						throw new Error("Character or Byte required.");
					};
				}
				else if (Type.isNumber(o))
				{
					if (o < 0)
					{
						o = - o;
						sign = "-";
					}
					else if (frmt.signed)
					{
						sign = "+";
					}
					else
					{
						sign = "";
					};

					switch (frmt.type)
					{
						case "f":
						case "F":
							if (frmt.percision > - 1)
							{
								s = (o.toFixed(frmt.percision)).toString();
							}
							else
							{
								s = o.toString();
							};
							break;

						case "E":
						case "e":
							if (frmt.percision > - 1)
							{
								s = o.toExponential(frmt.percision);
							}
							else
							{
								s = o.toExponential();
							};
							s = s.replace("e", frmt.type);
							break;

						case "b":
							s = o.toString(2);
							s = PadStr(s, "0", frmt.percision);
							break;

						case "o":
							s = o.toString(8);
							s = PadStr(s, "0", frmt.percision);
							break;

						case "x":
							s = o.toString(16).toLowerCase();
							s = PadStr(s, "0", frmt.percision);
							break;

						case "X":
							s = o.toString(16).toUpperCase();
							s = PadStr(s, "0", frmt.percision);
							break;

						default:
							s = parseInt(o).toString();
							s = PadStr(s, "0", frmt.percision);
							break;
					}
					;

					if (frmt.paddingFlag == "0")
					{
						s = PadStr(s, "0", frmt.minLength - sign.length);
					}
					s = sign + s;
					s = PadStr(s, frmt.paddingFlag, frmt.percision);
				}
				else
				{
					throw new Error("Number required.");
				};
			};
			rslt += s;
		};
		return rslt;
	}
};
function FormatSpecifier(s)
{
	var s = s.match(/%(\(\w+\)){0,1}([ 0-]){0,1}(\+){0,1}(\d+){0,1}(\.\d+){0,1}(.)/);

	this.key = s[1] ? s[1].slice(1, - 1) : null;
	this.paddingFlag = s[2];

	if (this.paddingFlag == "")
	{
		this.paddingFlag = " ";
	}
	this.signed = (s[3] == "+");
	this.minLength = parseInt(s[4]);

	if (isNaN(this.minLength))
	{
		this.minLength = 0;
	}
	
	this.percision = s[5] ? parseInt(s[5].slice(1, s[5].length)) : -1;

	this.type = s[6];
}

function PadStr(str, flag, len)
{
	var s = "", c, i = 0;
	
	c = (flag == "-") ? " " : flag;

	for (; i < len - str.length; i++)
	{
		s += c;
	}

	if (flag == "-")
	{
		s = str + s;
	}
	else
	{
		s += str;
	}
	return s;
}